package net.sf.ivyide.idea;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.ModifiableModuleModel;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModifiableRootModel;
import com.intellij.openapi.roots.ModuleCircularDependencyException;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.libraries.LibraryTable;
import net.sf.ivyide.ModuleManager;
import net.sf.ivyide.ModuleManagerFactory;
import net.sf.ivyide.ResolvedModule;

import java.io.File;
import java.util.*;

public class UpdateModules implements ProgressAwareCallable<CommitInfo> {
/*
    private static final Logger LOGGER = Logger.getInstance(UpdateModules.class.getName());

    private Project m_project;
    private IvyProjectSettings m_settings;
    private static final String GENERATE_BY_IVY = "generate.by.ivy";


    public UpdateModules(final IvyProjectSettings settings, Project project) {
        m_settings = settings;
        m_project = project;
    }

    public CommitInfo call(ProgressIndicator progressIndicator) throws Exception {
        progressIndicator.setText("Initializing ivy..");
        ModuleManager ivyEnvironment = m_settings.m_environmentInfo.createModuleProvider();

        List<IvyResolvedModule> resolvedModules = new ModuleResolver().getResolvedModules(m_settings,ivyEnvironment, progressIndicator);

        //ModuleManager instance = ModuleManager.getInstance(m_project);
        //final ModifiableModuleModel modulesModel = instance.getModifiableModel();

        ModifiableModuleModel modulesModel = getModuleModel(progressIndicator);
        final String projectDir = IntellijUtil.getBaseDir(m_project);

        final LibraryTable projectLibraryTable = m_project.getComponent(LibraryTable.class);
        final Map<String, Library> projectLibrariesUsedByAnyModule = new HashMap<String, Library>();

        double maxResolves = resolvedModules.size();
        double syncedCount = 0;
        progressIndicator.setIndeterminate(false);
        progressIndicator.setFraction(0.1);
        final Set<Module> usedModules = new HashSet<Module>();
        LOGGER.info("Starting sunc");
        Map<IvyResolvedModule, ModifiableRootModel> ivyToIntellijModelMap = new LinkedHashMap<IvyResolvedModule, ModifiableRootModel>();
        //first create all intellij modules. Sync them later. Then we can already apply the correct dependencies during syncing..
        final List<ModifiableRootModel> modulesToCommit = new LinkedList<ModifiableRootModel>();
        for (final IvyResolvedModule resolvedModule : resolvedModules) {
            //m_indicator.pushState();
            //  try {
            String moduleName = resolvedModule.getModuleId().getName();
            final File modulePath = new File(projectDir, "ivymodules/" + moduleName + ".iml");
            //IvyDependenciesStep.<Void>executeRunnableWithProgress(m_component, "Synchronizing module " + resolvedModule.getModuleId().getName(), "Oeps", new Callable() {
            //    public Object call() throws Exception {
            //indicator.setText("pinh " + resolvedModule.getModuleId().getName());
            //IvyProjectComponent.runWriteActionInSwingThread(new Runnable() {
            //   public void run() {

            //must be executed on swing thread (ModifiableModuleModel#newModule creates a write action)
            Module module = modulesModel.findModuleByName(moduleName);
            boolean loaded = false;
            if (module != null) {
                LOGGER.info("Found module " + module.getName());
                loaded = true;
            } else {
                if (modulePath.exists()) {
                    LOGGER.info("Loading module from " + modulePath);

                    module = ProgressUtilities.executeAsWriteActionOnEventQueue(new LoadModule(modulesModel, modulePath), progressIndicator);
                } else {
                    LOGGER.info("Creating new module " + modulePath);
                    module = ProgressUtilities.executeAsWriteActionOnEventQueue(new CreateNewModule(modulesModel, modulePath.getPath()), progressIndicator);
                    //ProgressUtilities.executeAsWriteActionOnEventQueue(new CommitModuleModel(modulesModel), progressIndicator);
                    //after commit, get new instance..
                    //modulesModel = getModuleModel(progressIndicator);
                }
            }
            module.setOption(GENERATE_BY_IVY, "true");
            usedModules.add(module);
            module.setSavePathsRelative(true);
            final Module module1 = module;
            final ModifiableRootModel modifiableModel = ProgressUtilities.executeAsReadAction(new ProgressAwareCallable<ModifiableRootModel>() {
                public ModifiableRootModel call(ProgressIndicator indicator) throws Exception {
                    return ModuleRootManager.getInstance(module1).getModifiableModel();
                }
            }, progressIndicator);
            //setup root model
            //if (modifiableModel.i)
            LOGGER.info("modifiableModel = " + modifiableModel);
            LOGGER.info("modifiableModel.getModule().getName() = " + modifiableModel.getModule().getName());
            modifiableModel.inheritJdk();
            ivyToIntellijModelMap.put(resolvedModule, modifiableModel);
        }
        //lets go syncing..
        Set<Library.ModifiableModel> libraryModelsToCommit = new LinkedHashSet<Library.ModifiableModel>();

        for (Map.Entry<IvyResolvedModule, ModifiableRootModel> resolvedModuleModifiableRootModelEntry : ivyToIntellijModelMap.entrySet()) {
            final ModifiableRootModel modifiableModel = resolvedModuleModifiableRootModelEntry.getValue();
            IvyResolvedModule resolvedModule = resolvedModuleModifiableRootModelEntry.getKey();
            final UpdateModule updateModule = new UpdateModule(ivyEnvironment, resolvedModule, modifiableModel, projectLibraryTable, projectLibrariesUsedByAnyModule, modulesModel, libraryModelsToCommit);
            updateModule.run(progressIndicator);
            //even new ones need to be committed !!
            boolean isChanged = true;
            ProgressUtilities.executeAsReadAction(new ProgressAwareCallable<Boolean>() {
                public Boolean call(ProgressIndicator indicator) throws Exception {
                    return modifiableModel.isChanged();
                }
            }, progressIndicator);
            if (isChanged) {//isChanges is not always true...
                //modulesToCommit.add(modifiableModel);
                ProgressUtilities.executeAsWriteActionOnEventQueue(new CommitRootModel(modulesModel, modifiableModel), progressIndicator);
                modulesModel = getModuleModel(progressIndicator);
            }
            double fraction = ++syncedCount / maxResolves;
            progressIndicator.setFraction(fraction);
        }
        progressIndicator.setIndeterminate(true);
        LOGGER.info("Cleaning ");
        progressIndicator.setText("Cleaning unused modules");
        ProgressUtilities.executeAsReadAction(new RemoveUnsedModulesFromModel(modulesModel, usedModules), progressIndicator);
        progressIndicator.setText("Finalizing project...");
        ProgressUtilities.executeAsWriteActionOnEventQueue(new ProgressAwareRunnable() {
            public void run(ProgressIndicator progressIndicator) {
                UpdateModule.cleanUnusedLibraries(projectLibrariesUsedByAnyModule.values(), projectLibraryTable);
            }
        }, progressIndicator);
        LOGGER.info("Done");
        return new CommitInfo(modulesToCommit, modulesModel, libraryModelsToCommit);
    }

    private ModifiableModuleModel getModuleModel(ProgressIndicator progressIndicator) {
        return ProgressUtilities.executeAsReadAction(new ProgressAwareCallable<ModifiableModuleModel>() {
            public ModifiableModuleModel call(ProgressIndicator progressIndicator) throws Exception {
                ModuleManager instance = ModuleManager.getInstance(m_project);
                return instance.getModifiableModel();
            }
        }, progressIndicator);
    }

    private static class CreateNewModule implements ProgressAwareCallable<Module> {
        private final ModifiableModuleModel modulesModel;
        private final String modulePath;

        public CreateNewModule(ModifiableModuleModel modulesModel, String modulePath) {
            this.modulesModel = modulesModel;
            this.modulePath = modulePath;
        }

        public Module call(ProgressIndicator progressIndicator) throws Exception {
            return modulesModel.newModule(modulePath);
        }
    }

    private static class CommitModuleModel implements ProgressAwareCallable<ModifiableModuleModel> {
        private final ModifiableModuleModel modulesModel;

        public CommitModuleModel(ModifiableModuleModel modulesModel) {
            this.modulesModel = modulesModel;
        }

        public ModifiableModuleModel call(ProgressIndicator indicator) throws Exception {
            try {
                modulesModel.commit();
            } catch (ModuleCircularDependencyException e) {
                throw new RuntimeException(e);
            }
            return modulesModel;
        }
    }

    private static class RemoveUnsedModulesFromModel implements ProgressAwareCallable<Void> {
        private final ModifiableModuleModel modulesModel;
        private final Set<Module> usedModules;

        public RemoveUnsedModulesFromModel(ModifiableModuleModel modulesModel, Set<Module> usedModules) {
            this.modulesModel = modulesModel;
            this.usedModules = usedModules;
        }

        public Void call(ProgressIndicator progressIndicator) throws Exception {
            for (Module module : modulesModel.getModules()) {
                if (!usedModules.contains(module)) {
                    if ("true".equals(module.getOptionValue(GENERATE_BY_IVY))) {
                        modulesModel.disposeModule(module);
                    }
                }
            }
            return null;
        }
    }

    private class CommitRootModel implements ProgressAwareRunnable {
        private final ModifiableModuleModel modulesModel;
        private final ModifiableRootModel modifiableModel;

        public CommitRootModel(ModifiableModuleModel modulesModel, ModifiableRootModel modifiableModel) {
            this.modulesModel = modulesModel;
            this.modifiableModel = modifiableModel;
        }

        public void run(ProgressIndicator progressIndicator) {
            //Is this needed ? Isn't this done when commiting at the modulesModel at the end ?
            //if not done, multiple facets can be detectedn (when adding previously created modules),
            //but new module are empty !!
            //why do we need to do commit here(in loop) instead of just afterwards ??
            //LOGGER.debug("Committing " + modifiableModel.getModule().getName());
            ProjectRootManagerEx.getInstanceEx(m_project).multiCommit(modulesModel, new ModifiableRootModel[]{modifiableModel});
            //modifiableModel.commit();
        }
    }

    private static class LoadModule implements ProgressAwareCallable<Module> {
        private final ModifiableModuleModel modulesModel;
        private final File modulePath;

        public LoadModule(ModifiableModuleModel modulesModel, File modulePath) {
            this.modulesModel = modulesModel;
            this.modulePath = modulePath;
        }

        public Module call(ProgressIndicator indicator) throws Exception {
            return modulesModel.loadModule(modulePath.getPath());
        }
    }  */

    private static final Logger LOGGER = Logger.getInstance(UpdateModules.class.getName());

    private Project m_project;
    private ModuleManagerFactory m_moduleManagerFactory;
    private static final String GENERATE_BY_IVY = "generate.by.ivy";

    public UpdateModules(final ModuleManagerFactory moduleManagerFactory, Project project) {
        m_moduleManagerFactory = moduleManagerFactory;
        m_project = project;
    }

    public CommitInfo call(ProgressIndicator indicator) throws Exception {
        indicator.setText("Initializing ivy..");

        ModuleManager moduleManager = m_moduleManagerFactory.createModuleManager();
        List<ResolvedModule> resolvedModules = new ModuleResolver().getResolvedModules(moduleManager, indicator);

        //ModuleManager instance = ModuleManager.getInstance(m_project);
        //final ModifiableModuleModel modulesModel = instance.getModifiableModel();

        final ModifiableModuleModel modulesModel = ProgressUtilities.executeAsWriteActionOnEventQueue(new ProgressAwareCallable<ModifiableModuleModel>() {
            public ModifiableModuleModel call(ProgressIndicator indicator) throws Exception {
                com.intellij.openapi.module.ModuleManager instance = com.intellij.openapi.module.ModuleManager.getInstance(m_project);
                return instance.getModifiableModel();
            }
        }, indicator);
        final String projectDir = IntellijUtil.getBaseDir(m_project);
        final LibraryTable projectLibraryTable = m_project.getComponent(LibraryTable.class);
        final Map<String, Library> projectLibrariesUsedByAnyModule = new HashMap<String, Library>();
        double maxResolves = resolvedModules.size();
        double syncedCount = 0;
        indicator.setIndeterminate(false);
        indicator.setFraction(0.001);
        final Set<Module> usedModules = new HashSet<Module>();
        LOGGER.info("Starting sunc");
        for (final ResolvedModule resolvedModule : resolvedModules) {
            //m_indicator.pushState();
            //  try {
            final String modulePath = new File(projectDir, "ivymodules/" + resolvedModule.getModuleId().getName() + ".iml").getPath();
            //IvyDependenciesStep.<Void>executeRunnableWithProgress(m_component, "Synchronizing module " + resolvedModule.getModuleId().getName(), "Oeps", new Callable() {
            //    public Object call() throws Exception {
            //indicator.setText("pinh " + resolvedModule.getModuleId().getName());
            //IvyProjectComponent.runWriteActionInSwingThread(new Runnable() {
            //   public void run() {

            final ModifiableRootModel modifiableModel = ProgressUtilities.executeAsWriteActionOnEventQueue(new ProgressAwareCallable<ModifiableRootModel>() {
                public ModifiableRootModel call(ProgressIndicator indicator) throws Exception {
                    Module moduleByPath = modulesModel.newModule(modulePath);
                    moduleByPath.setOption(GENERATE_BY_IVY, "true");
                    usedModules.add(moduleByPath);
                    moduleByPath.setSavePathsRelative(true);
                    final ModifiableRootModel modifiableModel = ModuleRootManager.getInstance(moduleByPath).getModifiableModel();
                    //setup root model
                    modifiableModel.inheritJdk();
                    return modifiableModel;
                }
            }, indicator);
            final UpdateModule updateModule = new UpdateModule(moduleManager, resolvedModule, modifiableModel, projectLibraryTable, projectLibrariesUsedByAnyModule, null, null);

            //IvyProjectComponent.runWriteActionInSwingThread(new Runnable() {
            //    public void run() {
            //ApplicationManager.getApplication().runProcessWithProgressSynchronously(updateModule, "Hello", false, null);
            updateModule.run(indicator);
            double fraction = ++syncedCount / maxResolves;
            indicator.setFraction(fraction);
            boolean isChanged = ProgressUtilities.executeAsReadAction(new ProgressAwareCallable<Boolean>() {
                public Boolean call(ProgressIndicator indicator) throws Exception {
                    return modifiableModel.isChanged();
                }
            }, indicator);
            LOGGER.debug("isChanged " + resolvedModule.getModuleId() + ": " + isChanged);
            if (isChanged) {
                ProgressUtilities.executeAsWriteActionOnEventQueue(new ProgressAwareRunnable() {
                    public void run(ProgressIndicator indicator) {
                        modifiableModel.commit();
                    }
                }, indicator);
            }
            //modifiableModel.commit();
            //  }

            //});
            //        return null;
            //    }
            //});
            //ApplicationManager.getApplication().invokeAndWait(new Runnable() {
            //    public void run() {
            //    }
            //}, ModalityState.defaultModalityState()

            //);

            //    } finally {
            //m_indicator.popState();
            //    }
        }
        LOGGER.info("Cleaning ");
        indicator.setIndeterminate(true);

        ProgressManager.getInstance().getProgressIndicator().setText("Cleaning unused modules");
        ProgressUtilities.executeAsReadAction(new ProgressAwareCallable<Void>() {
            public Void call(ProgressIndicator indicator) throws Exception {
                for (Module module : modulesModel.getModules()) {
                    if (!usedModules.contains(module)) {
                        if ("true".equals(module.getOptionValue(GENERATE_BY_IVY))) {
                            modulesModel.disposeModule(module);
                        }
                    }
                }
                //ApplicationManager.getApplication().invokeAndWait(new Runnable() {
                //    public void run() {
                //     }
                // }, ModalityState.defaultModalityState());
                ///IvyProjectComponent.runWriteActionInSwingThread(new Runnable() {
                //  public void run() {
                return null;
            }
        }, indicator);
        ProgressManager.getInstance().getProgressIndicator().setText("Finalizing project...");

        ProgressUtilities.executeAsWriteActionOnEventQueue(new ProgressAwareRunnable() {

            public void run(ProgressIndicator indicator) {
                UpdateModule.cleanUnusedLibraries(projectLibrariesUsedByAnyModule.values(), projectLibraryTable);
                if (modulesModel.isChanged()) {
                    try {
                        modulesModel.commit();
                    } catch (ModuleCircularDependencyException e) {
                        throw new RuntimeException(e);
                    }
                }
                //To change body of implemented methods use File | Settings | File Templates.
            }
        }, indicator);
        //}

        //});
        LOGGER.info("Done");
        return new CommitInfo(null, null, null);
    }

}
